﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace PatternSearch
{

    public class Element
    {
        public int TruePosition { get; set; }
        public int PositionStart { get; set; }
        public int PositionEnd { get; set; }

        public bool IsRoot { get; set; }
        public bool IsFacultative { get; set; }

        public bool FormNegation { get; set; }
        public bool LemmaNegation { get; set; }
        public bool SemtypeNegation { get; set; }

        public string Form { get; set; }
        public string Lemma { get; set; }
        public string Semtype { get; set; }

        public List<string> TAGS { get; set; }
        public List<string> RATAGS { get; set; }

        public Element Copy(int truePosition)
        {
            return new Element() 
            { 
                Form = this.Form,
                Lemma = this.Lemma,
                Semtype = this.Semtype,
                TAGS = this.TAGS,
                RATAGS = this.RATAGS,
                TruePosition = truePosition,
                PositionStart = this.PositionStart,
                PositionEnd = this.PositionEnd,
                IsRoot = this.IsRoot,
                IsFacultative = this.IsFacultative,
                FormNegation = this.FormNegation,
                LemmaNegation = this.LemmaNegation,
                SemtypeNegation = this.SemtypeNegation
            };
        }

        public bool ConformsTo(Word word)
        {
            if (Form != null && Form.Length > 0)
            {
                Regex form = new Regex(Form);
                var result = form.IsMatch(word.Form);

                if (FormNegation)
                    result = !result;

                if (!result)
                    return false;
            }

            if (Lemma != null && Lemma.Length > 0)
            {
                Regex lemma = new Regex(Lemma);
                var result = lemma.IsMatch(word.Lemma);

                if (LemmaNegation)
                    result = !result;

                if (!result)
                    return false;
            }

            if (Semtype != null && Semtype.Length > 0)
            {
                Regex semtype = new Regex(Semtype);
                var result = semtype.IsMatch(word.Semtype);

                if (SemtypeNegation)
                    result = !result;

                if (!result)
                    return false;
           }

            foreach (string tags in word.TAGS)
            {
                bool passed = true;

                if (TAGS != null)
                {
                    foreach (string tag in TAGS)
                    {
                        bool negation = Char.IsLower(tag, 0);
                        var gr = tag;

                        if (negation)
                            gr = Char.ToUpper(tag[0]).ToString() + tag[1];

                        var result = tags.Contains(gr);

                        if (negation)
                            result = !result;

                        if (!result)
                        {
                            passed = false;
                            break;
                        }

                    }
                }
                if (passed)
                    return true;
            }
            return false;
        }

        public bool RootAgreement(Word word, Word root)
        {
            if (RATAGS.Count > 0)
            {
                foreach (var wtag in word.TAGS)
                {
                    foreach (var rtag in root.TAGS)
                    {
                        bool passed = true;

                        foreach (var tag in RATAGS)
                        {
                            bool negation = Char.IsLower(tag, 0);
                            var gr = tag;

                            if (negation)
                                gr = Char.ToUpper(tag[0]).ToString() + tag[1];

                            var wIndex = wtag.IndexOf(gr[0]);
                            var rIndex = rtag.IndexOf(gr[0]);

                            if (wIndex > -1 && rIndex > -1)
                            {
                                var result = wtag[wIndex + 1] == rtag[rIndex + 1];

                                if (negation)
                                    result = !result;

                                if (!result)
                                {
                                    passed = false;
                                    break;
                                }
                            }
                        }

                        if (passed)
                            return true;
                    }
                }

                return false;
            }
            else
                return true;
        }

    }

    public class PatternWorker
    {
        public static Pattern[] LoadPatterns(string path)
        {
            StreamReader reader = new StreamReader(path, Encoding.UTF8);
            List<Pattern> patterns = new List<Pattern>();
            int id = 0;
            while (!reader.EndOfStream)
            {
                id++;
                List<Element> elements = new List<Element>();
                string[] elementsStr = SplitPatternString(reader.ReadLine());
                for (int i = 0; i < elementsStr.Count(); i++)
                {
                    elements.Add(ElementFromString(elementsStr[i], i + 1));
                }
                patterns.Add(new Pattern(id, elements));
            }
            return patterns.ToArray();
        }

        static string[] SplitPatternString(string pattern)
        {
            pattern = pattern.Trim();
            if (pattern.First() == '[')
                pattern = pattern.Remove(0, 1);
            if (pattern.Last() == ']')
                pattern = pattern.Remove(pattern.Length - 1, 1);

            return pattern.Split(new string[] { "][" }, StringSplitOptions.RemoveEmptyEntries);
        }

        static Element ElementFromString(string element, int truePosition)
        {
            string[] parameters = element.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

            string form = String.Empty;
            string lemma = String.Empty;
            string semtype = String.Empty;
            bool formNegation = false;
            bool lemmaNegation = false;
            bool semtypeNegation = false;
            int start = 0;
            int end = 0;
            bool isRoot = false;
            bool isFacultative = false;
            var tagList = new List<string>();
            var ratagList = new List<string>();

            foreach (string parameter in parameters)
            {
                string trimmed = parameter.Trim();
                if (trimmed.Length > 0)
                {
                    bool negation = false;
                    if (trimmed.First() == '!')
                    {
                        negation = true;
                        trimmed = trimmed.Remove(0, 1);
                    }

                    if (trimmed.First() == '"' && trimmed.Last() == '"')
                    {
                        trimmed = trimmed.Remove(0, 1);
                        trimmed = trimmed.Remove(trimmed.Length - 1, 1);
                        form = '^' + trimmed + '$';
                        formNegation = negation;
                    }
                    else if (trimmed.First() == '\'' && trimmed.Last() == '\'')
                    {
                        trimmed = trimmed.Remove(0, 1);
                        trimmed = trimmed.Remove(trimmed.Length - 1, 1);
                        lemma = '^' + trimmed + '$';
                        lemmaNegation = negation;
                    }
                    else if (trimmed.First() == '/' && trimmed.Last() == '/')
                    {
                        trimmed = trimmed.Remove(0, 1);
                        trimmed = trimmed.Remove(trimmed.Length - 1, 1);
                        semtype = '^' + trimmed + '$';
                        semtypeNegation = negation;
                    }
                    else if (trimmed.Contains(':') || Int32.TryParse(trimmed, out start) || trimmed == "R" || trimmed == "?")
                    {
                        if (trimmed == "R")
                            isRoot = true;
                        else if (trimmed == "?")
                            isFacultative = true;
                        else if (start != 0)
                            end = start;
                        else
                        {
                            string[] boundaries = trimmed.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
                            start = Int32.Parse(boundaries[0]);
                            end = Int32.Parse(boundaries[1]);
                        }
                    }
                    else
                    {
                        string buffer = String.Empty;
                        foreach (char ch in trimmed)
                        {
                            buffer += ch;
                            if (buffer.Count() == 2)
                            {
                                if (ch == '$')
                                    ratagList.Add(buffer);
                                else
                                    tagList.Add(buffer);
                                buffer = String.Empty;
                            }

                        }
                    }
                }
            }

            return new Element()
            {
                Form = form,
                Lemma = lemma,
                PositionEnd = end,
                PositionStart = start,
                TAGS = tagList,
                RATAGS = ratagList,
                TruePosition = truePosition,
                IsRoot = isRoot,
                IsFacultative = isFacultative,
                FormNegation = formNegation,
                LemmaNegation = lemmaNegation
            };
        }

    }

    public class Pattern
    {
        public Pattern(int id, List<Element> elements)
        {
            ID = id;
            foreach (var element in elements)
                if (element.IsRoot)
                {
                    Root = element;
                    elements.Remove(element);
                    break;
                }

            var leftElements = new List<Element>();
            var rightElements = new List<Element>();

            foreach (var element in elements)
            {
                var start = element.PositionStart;
                var end = element.PositionEnd;
                if (start == 0 && end == 0)
                {
                    start = element.TruePosition - Root.TruePosition;
                    end = start;
                }
                for (int i = start; i <= end; i++)
                    if (i < 0)
                        leftElements.Add(element.Copy(i));
                    else if (i > 0)
                        rightElements.Add(element.Copy(i));
            }

            LeftElements = leftElements.OrderByDescending(a => a.TruePosition).ToArray();
            RightElements = rightElements.OrderBy(a => a.TruePosition).ToArray();
        }

        public string TryGetConstruction(Word root, int rootIndex, List<Word> corpus)
        {
            var construction = new SortedDictionary<int, Word>();
            construction.Add(rootIndex, root);

            if (!SearchSide(construction, LeftElements, corpus, rootIndex, -1))
                return null;
            if (!SearchSide(construction, RightElements, corpus, rootIndex, 1))
                return null;

            string phrase = String.Empty;
            foreach (var pair in construction)
            {
                phrase += pair.Value.Form + " ";
            }
            return phrase;

        }

        // Direction: -1 - search left elements, +1 - search right elements
        private bool SearchSide(SortedDictionary<int, Word> construction, Element[] elements, List<Word> corpus, int rootIndex, int direction)
        {
            var offset = 0;
            foreach (var element in elements)
            {
                int realPosition = element.TruePosition + rootIndex + offset;
                if (realPosition > 0 && realPosition < corpus.Count())
                {
                    if (element.ConformsTo(corpus[realPosition]) && element.RootAgreement(corpus[realPosition], corpus[rootIndex]))
                        construction[realPosition] = corpus[realPosition];
                    else if (element.IsFacultative)
                        offset = offset - direction;
                    else
                        return false;
                }
                else
                    return false;
            }
            return true;
        }

        public int ID { get; set; }
        public Element Root { get; set; }
        public Element[] LeftElements { get; set; }
        public Element[] RightElements { get; set; }
    }

}